Repository navigation
ci: add ruff lint and bandit SAST, and SHA-pin all actions - #41
Merged
Merged
Conversation
Adopts the three patterns worth taking from trustedsec/hate_crack's CI. ruff: a new `lint` job runs `ruff check spoonmap.py tests/` against a deliberately narrow ruleset (E4, E7, E9, F) with ruff exact-pinned in the dev group, so a ruff release that grows an opinion cannot fail an unrelated PR. `ruff format` is not adopted: reformatting a 4.6k-line module and an 11k-line test file would bury every future diff, and E501 alone flags 288 existing lines. Its first run found 17 real issues, all fixed here rather than suppressed: - spoonmap.py: an ambiguous `l` loop variable and two f-strings with no placeholders - tests: 5 unused imports, 4 multiple-imports-on-one-line, a lambda bound to a name, another ambiguous `l`, and 3 dead locals Three of those dead locals were worth a second look rather than a blind --fix. Two adjacent tests each bound both `mock_m` and `mock_u` but asserted on only one, so the unused binding is dropped from each while the patch stays in place; the third discarded a mass_scan() return value the test never read. bandit: a new `bandit` job runs SAST against a committed baseline. A bare run reports 32 findings on spoonmap.py (12x B603 list-form subprocess, 12x B314 xml.etree on output from tools we invoked ourselves, plus B110/B112/B404/ B405/B607) — all reviewed and inherent to a scanner wrapper. Baselining them means only a NEW finding fails the build, which is the difference between a gate and a wall of noise. Verified both directions: clean against the baseline, and exit 1 with a fresh B307 when an eval() was temporarily added. Also verified the baseline is not line-number fragile, so unrelated edits that shift lines do not produce phantom findings. Actions are now pinned to commit SHAs with the tag in a trailing comment instead of mutable `@v4`/`@v6` tags, and every checkout sets `persist-credentials: false`. The pinned versions (checkout v7.0.1, setup-uv v10.0.1) were verified against the upstream tag refs, and being current also clears the Node 20 deprecation warning the old tags emitted. Verified locally: ruff clean; bandit clean against baseline; actionlint clean; uv lock --check clean; 971 passed / 5 skipped at 100% coverage on the locked 3.12 path and 99.91% on the unlocked 3.8 path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adopts the three patterns worth taking from hate_crack's CI, scaled to a single-module stdlib project. Skipped from that repo:
pip-audit(spoonmap has dev-only deps, so it would audit pytest and little else, and Dependabot already covers it),tytype checking, and all release/tag/PyPI automation (no releases here).ruff
New
lintjob:uv run --frozen ruff check spoonmap.py tests/.The ruleset is deliberately narrow —
E4,E7,E9,F— and ruff is exact-pinned (ruff==0.16.4) in thedevgroup, so a ruff release that grows a new opinion cannot fail an unrelated PR.ruff formatis not adopted: reformatting a 4.6k-line module and an 11k-line test file would bury every future diff under churn, andE501alone would flag 288 existing lines.The first run found 17 real issues. All are fixed here, none suppressed:
spoonmap.pylloop variable (E741); 2x f-string with no placeholders (F541)tests/l(E741); 3x dead local (F841)Three of the dead locals deserved a look rather than a blind
--fix, and one is a genuine (if harmless) test smell:TestMassScanUdp's two adjacent tests each bound bothmock_mandmock_uwhile asserting on only one. The unused binding is dropped from each — thepatch()stays, only theasname goes — so neither test loses a patch. The third discarded amass_scan()return value the test never read.bandit
New
banditjob, SAST against a committed.bandit-baseline.json.A bare run reports 32 findings on
spoonmap.py, none of them High: 12xB603(list-formsubprocess), 12xB314(xml.etreeon masscan/nmap output we invoked ourselves), plusB110/B112/B404/B405/B607. These are inherent to a scanner wrapper and already reviewed —CLAUDE.mddocuments the list-form-subprocess and defensive-XML-parsing invariants. Baselining them is what makes this a gate rather than a wall of noise, and it keeps the justification in git history instead of scattering# noseccomments through the module.I verified the gate works in both directions, since a SAST job that can only ever pass is worse than none:
def _bandit_probe(x): return eval(x)→ exit 1,B307reportedspoonmap.pyby 2 → still exit 0, so the baseline is not line-fragile and unrelated edits won't produce phantom findingsNote
xml.etree(B314) is baselined rather than fixed: switching todefusedxmlwould add a runtime dependency to a deliberately stdlib-only tool, and the XML being parsed is output from tools this process just spawned.Supply-chain hygiene
Actions are pinned to commit SHAs with the tag in a trailing comment, replacing mutable
@v4/@v6tags — a tag is rewritable, so@v4is effectively an unaudited third party with access to the build. Every checkout also getspersist-credentials: false.I verified the SHAs against the upstream tag refs rather than copying hate_crack's values on faith:
actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1=v7.0.1astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d=v10.0.1Being current on both also clears the Node 20 deprecation warning the old tags were emitting.
Verification
uv run --frozen ruff check spoonmap.py tests/→ All checks passedbandit -r spoonmap.py -c pyproject.toml -b .bandit-baseline.json→ exit 0actionlint .github/workflows/ci.yml→ 0 errorsuv lock --check→ in syncuv run --frozen --python 3.12 pytest tests/ -q→ 971 passed, 5 skipped, 100% coverageuv run --isolated --no-project --python 3.8 ... pytest tests/ -q→ 971 passed, 5 skipped, 99.91%Docs updated in README (
### Continuous integration, including the re-baseline command) andCLAUDE.md.